package com.onpositive.data.serializer.writers;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

import com.onpositive.data.IObjectCollection;
import com.onpositive.data.IPropertyConstants;
import com.onpositive.data.IPropertyMetadata;

@SuppressWarnings("rawtypes")
public class ObjectCollectionWriter extends AbstractWriter<IObjectCollection> {
	
	private static final int INT_SIZE = 4 ;
	
	public static final int OBJECT_COLLECTION_ID = Integer.MIN_VALUE ;
	public static final int TYPE_NULL_ID	 = 0;
	public static final int TYPE_STRING_ID   = 1;
	public static final int TYPE_INTEGER_ID  = 2;
	public static final int TYPE_DOUBLE_ID	 = 3;
	public static final int TYPE_FLOAT_ID	 = 4;
	public static final int TYPE_DATE_ID	 = 5;
	public static final int TYPE_BOOLEAN_ID	 = 6;

	public ObjectCollectionWriter() {
		super(IObjectCollection.class);
	}

	IWriterFactory factory;

	public IWriterFactory getFactory() {
		return factory;
	}

	public void setFactory(IWriterFactory factory) {
		this.factory = factory;
	}

	ArrayList<CollectionData> data = new ArrayList<ObjectCollectionWriter.CollectionData>();

	public static class CollectionData {
		public CollectionData( LinkedHashMap<IPropertyMetadata, AbstractWriter<?>> wMap2, int size )
		{
			this.wMap = wMap2;
			this.size = size ;
		}
		int size ;
		LinkedHashMap<IPropertyMetadata, AbstractWriter<?>> wMap;
	}


	@Override
	@SuppressWarnings("unchecked")
	public void append( IObjectCollection obj)
	{

		Collection<IPropertyMetadata> properties = obj.properties();
		LinkedHashMap<IPropertyMetadata, AbstractWriter<?>> wMap = new LinkedHashMap<IPropertyMetadata, AbstractWriter<?>>();
		int k = 0 ;
		for (IPropertyMetadata m : properties) {
			
			if( m == null )
				continue ;
			
			Object deprecatedObject = m.getMetaPropertyValue( IPropertyConstants.CHAR_DEPRICATED_LABEL );			
			if(deprecatedObject!=null){
				if((deprecatedObject instanceof Boolean)&&((Boolean)deprecatedObject) ){
					continue;
				}
				else if(Boolean.parseBoolean(deprecatedObject.toString())){
					continue;
				}					
			}			
			
			//System.out.print( k++ + ": property " + m.id() + "\n") ;
			if (m!=null){
			Object metaPropertyValue = m.getMetaPropertyValue( IPropertyConstants.CHAR_PARSER_ID_LABEL  );
			if( metaPropertyValue!=null&&metaPropertyValue.equals("text-section") )
				continue ;
			}
			
			AbstractWriter w = factory.createWriter(m);			
			
			if( w == null )
				continue ;			
			wMap.put(m, w);
			int i = 0 ;
			for (Object o : obj) {
				i++ ;
				Object value = obj.getValue(o, m);
				Object value0 = w.getClazz().isInstance( value ) ? value : null ;
				w.append(value0);
			}
		}
		data.add(new CollectionData( wMap, obj.size() ));
	}
	static class NullOutputStream extends OutputStream{

		@Override
		public void write(int b) throws IOException {
			
		}
		
	}

	@Override
	protected void doSerialize()
	{
		try{		
			int size = data.size() ;
			ArrayList<Integer> collectionOffsets = new ArrayList<Integer>( size + 1) ;
			int cOffset = getStream().size() + INT_SIZE * ( size + 1 + 1);
			collectionOffsets.add( cOffset ) ;
		
			for (CollectionData d : data)
			{
				DataOutputStream ds1=new DataOutputStream(new NullOutputStream());
				writeCollection(ds1, d, cOffset );
				int offset = ds1.size();
				cOffset+=offset ;
				collectionOffsets.add( cOffset ) ;
				ds1.close();
			}
			
			super.writeIntToStream( size ) ;
			for( Integer i : collectionOffsets )
				super.writeIntToStream( i ) ;
			
			for ( int i = 0 ; i < size ; i++)
				writeCollection( getStream(), data.get(i), collectionOffsets.get(i) );				
	
			getStream().close();
		}
		catch( IOException e )
		{
			e.printStackTrace() ;
		}
	}

	@SuppressWarnings("unused")
	private void writeCollection(DataOutputStream stream, CollectionData d, int collectionStartOffset )
			throws IOException {
		
		stream.writeInt(d.size );
		stream.writeInt(d.wMap.size());		
		int cOffset=stream.size() + INT_SIZE*(d.wMap.size()+1) - collectionStartOffset ;
		stream.writeInt( cOffset ) ;
		for (IPropertyMetadata metadata : d.wMap.keySet())
		{
			DataOutputStream ds1=new DataOutputStream(new NullOutputStream());
			writeMetadata(ds1, metadata);
			int offset = ds1.size();
			cOffset+=offset ;
			stream.writeInt( cOffset );
			ds1.close();
		}
		for (IPropertyMetadata metadata : d.wMap.keySet())
			writeMetadata(stream, metadata);
		
		cOffset=stream.size() + INT_SIZE*(d.wMap.size()+1) - collectionStartOffset ;
		stream.writeInt( cOffset ) ;
		for ( Map.Entry<IPropertyMetadata,AbstractWriter<?>> entry : d.wMap.entrySet() ){
			AbstractWriter q = entry.getValue() ;
			IPropertyMetadata meta = entry.getKey() ;
			DataOutputStream ds1=new DataOutputStream(new NullOutputStream());
			q.serialize(ds1);
			int offset = ds1.size();
			cOffset+=offset ;
			stream.writeInt( cOffset );				
			ds1.close();
		}	
		for ( Map.Entry<IPropertyMetadata,AbstractWriter<?>> entry : d.wMap.entrySet() ){
			AbstractWriter q = entry.getValue() ;
			IPropertyMetadata meta = entry.getKey() ;
			q.serialize(stream);
		}
	}

	private void writeMetadata( DataOutputStream ds, IPropertyMetadata metadata ) throws IOException
	{
		String id = metadata.id() ;
		ds.writeUTF( id );
		writeTypeCode( metadata.type(), ds );
		
		Collection<String> keys = metadata.keys();
		
		if( keys == null ){
			ds.writeInt( 0 ) ;
			return ;
		}
		
		ds.writeInt( keys.size() ) ;
		for ( String s : keys )
		{
			ds.writeUTF(s);
			Object value = metadata.getMetaPropertyValue(s);
			writeUntypedValue(value, ds);
		}
	}

	private void writeTypeCode(Class<?> type, DataOutputStream stream) throws IOException
	{
		if (type==null){			
			stream.writeInt(TYPE_NULL_ID);
			return ;
		}
		if (type==String.class){
			stream.writeInt(TYPE_STRING_ID);
			return ;
		}
		if (type==Integer.class){		
			stream.writeInt(TYPE_INTEGER_ID);
			return ;
		}
		if (type==Double.class){		
			stream.writeInt(TYPE_DOUBLE_ID);
			return ;
		}
		if (type==Float.class){		
			stream.writeInt(TYPE_FLOAT_ID);
			return ;
		}
		if (type==Date.class){	
			stream.writeInt(TYPE_DATE_ID);
			return ;
		}
		if (type==Boolean.class){
			stream.writeInt(TYPE_BOOLEAN_ID);
			return ;
		}
	}

	private void writeUntypedValue(Object value, DataOutputStream stream) throws IOException
	{
		writeTypeCode( value==null?null:value.getClass(), stream );
		
		if (value instanceof String){
			stream.writeUTF((String) value);
			return ;
		}

		if (value instanceof Integer){
			stream.writeInt((Integer) value);
			return ;
		}
		if (value instanceof Double){
			stream.writeDouble((Double) value);
			return ;
		}
		if (value instanceof Float){
			stream.writeFloat((Float) value);
			return ;
		}
		if (value instanceof Date){
			stream.writeLong(((Date) value).getTime());
			return ;
		}
		if (value instanceof Boolean){
			stream.writeBoolean((Boolean) value);
			return ;
		}
	}

	@Override
	protected int getID(){ return OBJECT_COLLECTION_ID ;}

}
